home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac-Source 1994 July
/
Mac-Source_July_1994.iso
/
C and C++
/
Libraries
/
CPEditText 1.2
/
CPEditText.cp
< prev
next >
Wrap
Text File
|
1993-10-03
|
101KB
|
3,520 lines
/******************************************************************************
CPEditText.cp
The PEditText Class
SUPERCLASS = CAbstractText
---- DESCRIPTION ----
CPEditText is a class for version 1.1.x of Symantec's THINK Class
Library that implements a simple text editing pane. It can be used as
a direct replacement for the standard TCL CEditText class, provided
that the word wrapping and alignment features of CEditText are not used.
However, since CPEditText does not use the standard Macintosh TextEdit
routines, it supports fixed-width tabs and can be used to display and
edit more than 32k of text.
---- LIMITATIONS ----
CPEditText is designed to implement an MPW-style text editor, and as
such it supports only a single font, size and style for text and does
not support word-wrapping or alignment. The code also does not use
the Macintosh Script Manager routines, so it may not work properly
with certain international versions of System software. A future
release of this source code may address some or all of these
limitations.
---- VERSION HISTORY ----
• 1.0b1 (27 April 1992)
- Initial public release
• 1.0b2 (30 April 1992)
- Fixed cosmetic bug in DoClick that caused insertion caret
to not get erased when selection range changed
• 1.1b1 (17 October 1992)
- Added support for insertion buffer ("gap")
- Added IViewTemp, IPEditTextX, and CreateEnvironment methods
• 1.1b2 (24 November 1992)
- Added CheckInsertion method to "preflight" insertions
- Added InsertText method and modified InsertTextPtr and
ReplaceSelection methods to bottleneck through InsertText
- Added UseTextHandle method
- Added CalcLineHeight and CalcTabWidth methods
- Created CPEditTextX.h header file for constants and macros
• 1.1 (10 December 1992)
- Added typecasts to enable code to be compiled with "check
pointer types" option on
• 1.2b1 (21 May 1993)
- Changes to compile under Symantec C++/TCL 1.1.3
- Added HideSelection, ScrollToSelection, ScrollToOffset and
GetChar methods
- Now check that pane is visible in Dawdle method
- Now scroll when extending selection via Shift-up/down arrow
- Fixed various minor bugs
• 1.2b2 (2 June 1993)
- Modified Draw, Activate, Deactivate and HideSelection methods
to inhibit display of caret for noneditable text
• 1.2b3 (4 June 1993)
- Removed hook instance variables and class methods and replaced
them with virtual hook methods
• 1.2b4 (28 August 1993)
- Added DeleteText and CountRangeCRs methods
- Changed behavior of DoArrowKey method to conform to Apple's
Human Interface Guidelines
• 1.2 (9 September 1993)
- Fixed bugs in Activate, Deactivate and TypeKey methods
---- LICENSE AGREEMENT ----
This source code was written by and is the property of Christopher R.
Wysocki. It may be freely distributed and used, in whole or in part, in
any software for the Macintosh, including but not limited to commercial,
shareware, freeware or private applications. However, any software that
uses any part or all of this source code must include a copyright notice
indicating that part or all of this source code is used in the software.
You are entitled to make modifications or improvements to any portion of
this source code; you may not, however, distribute modified versions of
this source code. Bug reports, suggestions, or general comments
regarding this source code are encouraged; the author can be contacted at
the electronic mail addresses listed below. This license agreement and
the copyright notice below must not be modified in any way and must be
included with all distributions of this source code at all times.
Copyright © 1992-1993 Christopher R. Wysocki. All rights reserved.
---- ELECTRONIC MAIL ADDRESSES ----
America Online: AFA ChrisW (preferred)
CompuServe: 72010,1140
Internet: wysocki@netcom.com (preferred)
afachrisw@aol.com
72010.1140@compuserve.com
******************************************************************************/
/** Includes **/
#include "CPEditText.h"
#include "CPEditTextX.h"
#include <CClipboard.h>
#include <CScrollPane.h>
#include <CTextEnvirons.h>
#include <Constants.h>
#include <Commands.h>
#include <LongCoordinates.h>
#include <Global.h>
#include <TBUtilities.h>
#include <TCLUtilities.h>
#if THINK_C
#include <asm.h>
#endif
#include <GestaltEqu.h>
#include <Palettes.h>
#include <Script.h>
/** Constants **/
enum {
kUnhideSelection = FALSE,
kHideSelection = TRUE,
kRefreshAllTextAfter = FALSE,
kRefreshOnlyLine = TRUE
};
/** Global Variables **/
extern CBureaucrat *gGopher; // First in line to get commands
extern CClipboard *gClipboard; // Copies and Pastes data
extern CursHandle gIBeamCursor; // I-beam cursor for text views
extern CursHandle gWatchCursor; // Watch cursor for waiting
extern short gClicks; // Click counter
extern RgnHandle gUtilRgn; // Utility region
/** Class Variables **/
CursHandle CPEditText::cItalicIBeamCursor = NULL;
RgnHandle CPEditText::cSaveClipRgn = NULL;
/** Local Prototypes **/
static long CountCRs(Ptr textP, long numChars);
static Boolean GetGrayRGBColor(const Rect *localRect, RGBColor *grayColor, RGBColor *prevForeColor);
static Boolean GestaltHasAttr(OSType selector, short responseBit);
/**** C O N S T R U C T I O N / D E S T R U C T I O N M E T H O D S ****/
/******************************************************************************
IPEditText
Initialize a PEditText object.
******************************************************************************/
void CPEditText::IPEditText(
CView *anEnclosure,
CBureaucrat *aSupervisor,
short aWidth,
short aHeight,
short aHEncl,
short aVEncl,
SizingOption aHSizing,
SizingOption aVSizing)
{
CAbstractText::IAbstractText(anEnclosure, aSupervisor, aWidth, aHeight,
aHEncl, aVEncl, aHSizing, aVSizing, kDefaultBoundsWidth);
IPEditTextX();
}
/******************************************************************************
IViewTemp {OVERRIDE}
Initialize a PEditText object from a resource template.
******************************************************************************/
void CPEditText::IViewTemp(CView *anEnclosure, CBureaucrat *aSupervisor, Ptr viewData)
{
inherited::IViewTemp(anEnclosure, aSupervisor, viewData);
IPEditTextX();
}
/******************************************************************************
IPEditTextX
Extra initialization for a PEditText object. Called by IPEditText
and IViewTemp.
******************************************************************************/
void CPEditText::IPEditTextX()
{
UseLongCoordinates(TRUE);
SetWholeLines(TRUE);
// Initialize instance variables
itsTextHandle = NewHandle(0);
itsTextLength = 0;
itsNumLines = 1;
itsLineStarts = (LongHandle)NewHandle(sizeof(long));
**itsLineStarts = 0;
#if qPEUseInsertionGap
itsGapPosition = itsGapLength = 0;
#endif
itsTextFont = GetAppFont();
itsTextSize = GetDefFontSize();
itsTextFace = normal;
itsTextMode = srcOr;
itsTabSpaces = kDefaultTabSpaces;
itsSpacingCmd = cmdSingleSpace;
itsSelStart = itsSelEnd = itsSelAnchor = 0;
fOutlineHilite = FALSE;
fUseItalicCaret = FALSE;
fShowInvisibles = FALSE;
itsClickTime = itsCaretTime = 0;
fCaretVisible = FALSE;
fReallyActive = FALSE;
fUpDownArrow = FALSE;
itsUpDownHOffset = 0;
#if !qTCL113
scrollHoriz = FALSE;
#endif
CreateEnvironment();
// Initialize class variables
cItalicIBeamCursor = GetCursor(rItalicIBeamCursor);
if (cItalicIBeamCursor != NULL)
HNoPurge((Handle)cItalicIBeamCursor);
if (cSaveClipRgn == NULL)
cSaveClipRgn = NewRgn();
}
/******************************************************************************
CreateEnvironment
Create and initialize the text environment.
******************************************************************************/
void CPEditText::CreateEnvironment()
{
CTextEnvirons *textEnvirons;
TextInfoRec textInfo;
textEnvirons = new CTextEnvirons;
textEnvirons->ITextEnvirons();
textInfo.fontNumber = itsTextFont;
textInfo.theSize = itsTextSize;
textInfo.theStyle = itsTextFace;
textInfo.theMode = itsTextMode;
textEnvirons->SetTextInfo(&textInfo);
itsEnvironment = textEnvirons;
CalcLineHeight();
}
/******************************************************************************
Dispose {OVERRIDE}
Dispose of a PEditText object.
******************************************************************************/
void CPEditText::Dispose()
{
ForgetHandle(itsTextHandle);
ForgetHandle(itsLineStarts);
inherited::Dispose();
}
/**** D I S P L A Y M E T H O D S ****/
/******************************************************************************
Draw {OVERRIDE}
Draw the contents of the PEditText pane.
******************************************************************************/
void CPEditText::Draw(Rect *area)
{
LongRect longArea;
long startLine;
long endLine;
long vertInset = VertInset();
// Compute and draw the visible text lines
QDToFrameR(area, &longArea);
startLine = (longArea.top - vertInset) / itsLineHeight;
endLine = (longArea.bottom - vertInset) / itsLineHeight;
endLine = Min(endLine, itsNumLines - 1);
DrawLineRange(startLine, endLine, 0, kDontEraseText);
// If printing is not in progress, highlight the current selection
// or draw the insertion caret, as appropriate
if (!this->printing && this->wantsClicks && (fReallyActive || fOutlineHilite)) {
if (itsSelStart != itsSelEnd)
HiliteTextRange(itsSelStart, itsSelEnd);
else if (fCaretVisible)
DrawCaret();
}
}
/******************************************************************************
Activate {OVERRIDE}
Activate a PEditText pane by hiliting the selection or showing
the text insertion caret.
******************************************************************************/
void CPEditText::Activate()
{
Boolean wasActive = fReallyActive;
// Remove the outlined selection range or erase the inactive caret
if (!wasActive)
HideSelection(kHideSelection, kRedraw);
// Set instance variable to indicate that we're really active
fReallyActive = TRUE;
// Call the inherited method to activate the pane
inherited::Activate();
// Hilite the now active selection range, if appropriate
if (!wasActive && this->wantsClicks && (this->editable || (itsSelStart != itsSelEnd)))
HideSelection(kUnhideSelection, kRedraw);
}
/******************************************************************************
Deactivate {OVERRIDE}
Deactivate a PEditText pane by unhiliting the selection or hiding
the text insertion caret.
******************************************************************************/
void CPEditText::Deactivate()
{
Boolean wasActive = fReallyActive;
// Unhilite the selection range or erase the insertion caret
if (wasActive)
HideSelection(kHideSelection, kRedraw);
// Set instance variable to indicate that we're not really active
fReallyActive = FALSE;
// Call the inherited method to deactivate the pane
inherited::Deactivate();
// Outline the now inactive selection range, if appropriate
if (wasActive && fOutlineHilite && this->wantsClicks && (this->editable || (itsSelStart != itsSelEnd)))
HideSelection(kUnhideSelection, kRedraw);
}
/******************************************************************************
SetSelection {OVERRIDE}
Sets the selected text to the range corresponding to character
positions selStart through selEnd.
******************************************************************************/
void CPEditText::SetSelection(long selStart, long selEnd, Boolean redraw)
{
// Ensure that the positions are valid
selStart = Max(selStart, 0);
selStart = Min(selStart, itsTextLength);
selEnd = Max(selEnd, 0);
selEnd = Min(selEnd, itsTextLength);
selEnd = Max(selEnd, selStart);
// Take a quick exit if the selection range isn't being changed
if ((selStart == itsSelStart) && (selEnd == itsSelEnd))
return;
// Unhilite the old selection range
HideSelection(kHideSelection, redraw);
// Update our instance variables
itsSelStart = selStart;
itsSelEnd = selEnd;
fUpDownArrow = FALSE;
// Ensure that the anchor point is one of the selection endpoints
if ((itsSelAnchor != selStart) && (itsSelAnchor != selEnd))
itsSelAnchor = selStart;
// Hilite the new selection range
if (this->editable || (selStart != selEnd))
HideSelection(kUnhideSelection, redraw);
}
/******************************************************************************
HideSelection {OVERRIDE}
Hide/unhide the current selection and the blinking cursor.
Does not change the active or gopher state.
******************************************************************************/
void CPEditText::HideSelection(Boolean hide, Boolean redraw)
{
if (redraw) {
Prepare();
if (itsSelStart != itsSelEnd)
HiliteTextRange(itsSelStart, itsSelEnd);
else {
if (hide)
HideCaret();
else
ShowCaret();
}
}
}
/******************************************************************************
ScrollToSelection {OVERRIDE}
Scroll the text so that the current selection is visible within
the frame. This method will scroll the text so that the selection
is in the middle of the frame.
******************************************************************************/
void CPEditText::ScrollToSelection()
{
long startLine, endLine;
short hSpan, vSpan;
LongPt selPos, selPt;
long selStart, selEnd;
// Calculate the number of panorama units spanned by the frame
GetFrameSpan(&hSpan, &vSpan);
// Determine the starting and ending lines for the selection
GetSelection(&selStart, &selEnd);
startLine = FindLine(selStart);
endLine = (selStart == selEnd ? startLine : FindLine(selEnd));
// Calculate the new vertical scroll position
if (startLine >= position.v + vSpan) {
selPos.v = startLine - (vSpan / 2) + 1;
selPos.v = Max(selPos.v, 0);
selPos.v = Min(selPos.v, itsNumLines - vSpan);
}
else if (endLine < position.v) {
selPos.v = endLine - (vSpan / 2) + 1;
selPos.v = Max(selPos.v, 0);
selPos.v = Min(selPos.v, itsNumLines - vSpan);
}
else
selPos.v = position.v;
// Calculate the new horizontal scroll position
if (!scrollHoriz)
selPos.h = position.h;
else {
GetCharPoint(selPos.v == startLine ? selStart : selEnd, &selPt);
selPos.h = (selPt.h / hScale) - (hSpan / 2);
selPos.h = Max(0, selPos.h);
}
// Scroll the pane if necessary
if ((selPos.v != position.v) || (selPos.h != position.h))
ScrollTo(&selPos, kRedraw);
}
/******************************************************************************
ScrollToOffset
Scroll the text so that the character at the given offset is visible
within the frame. Unlike ScrollToSelection, this method will scroll
the text as little as is necessary to make the character visible.
******************************************************************************/
void CPEditText::ScrollToOffset(long charOffset)
{
long charLine;
short hSpan, vSpan;
LongPt selPos, selPt;
// Calculate the number of panorama units spanned by the frame
GetFrameSpan(&hSpan, &vSpan);
// Calculate the new vertical scroll position
charLine = FindLine(charOffset);
if (charLine >= position.v + vSpan)
selPos.v = charLine - vSpan + 1;
else if (charLine < position.v)
selPos.v = charLine;
else
selPos.v = position.v;
// Calculate the new horizontal scroll position
if (!scrollHoriz)
selPos.h = position.h;
else {
GetCharPoint(charOffset, &selPt);
selPos.h = selPt.h / hScale;
if (selPos.h >= position.h + hSpan)
selPos.h -= (hSpan * 3) / 4;
else if (selPos.h < position.h)
selPos.h -= hSpan / 4;
else
selPos.h = position.h;
selPos.h = Max(selPos.h, 0);
}
// Scroll the pane if necessary
if ((selPos.v != position.v) || (selPos.h != position.h))
ScrollTo(&selPos, kRedraw);
}
/**** C U R S O R M E T H O D S ****/
/******************************************************************************
AdjustCursor {OVERRIDE}
Adjust the shape of the cursor according the position of the mouse.
******************************************************************************/
void CPEditText::AdjustCursor(Point where, RgnHandle mouseRgn)
{
if ((itsTextFace & italic) && fUseItalicCaret && (cItalicIBeamCursor != NULL))
SetCursor(*cItalicIBeamCursor);
else
SetCursor(*gIBeamCursor);
}
/******************************************************************************
Dawdle {OVERRIDE}
The user isn't doing anything, so flash the text insertion caret.
Also check if the insertion gap needed to be repositioned and/or
resized.
******************************************************************************/
void CPEditText::Dawdle(long *maxSleep)
{
long ticks;
if (editable && visible) {
// Flash the insertion caret
if ((itsSelStart == itsSelEnd) && ((ticks = TickCount()) >= itsCaretTime)) {
Prepare();
DrawCaret();
fCaretVisible = !fCaretVisible;
itsCaretTime = ticks + GetCaretTime();
*maxSleep = GetCaretTime();
}
#if qPEUseInsertionGap
// Move and resize the insertion gap
SetGapPosition(itsSelStart);
if ((itsGapLength < kStandardGapLength / 2) || (itsGapLength > kStandardGapLength * 2))
SetGapLength(kStandardGapLength);
#endif
}
}
/**** M O U S E A N D K E Y S T R O K E M E T H O D S ****/
/******************************************************************************
DoClick {OVERRIDE}
Respond to a mouse click within the PEditText. A single click
positions the insertion point, a double click selects a word, a
triple click selects a line or paragraph, and a quadruple click
selects all of the text.
******************************************************************************/
void CPEditText::DoClick(Point hitPt, short modifierKeys, long when)
{
LongPt framePt;
LongPt autoScrollPt;
long selStart, selEnd;
long charOffset, origCharOffset, lastCharOffset;
long newSelStart, newSelEnd;
long vertInset = VertInset();
// Get the current selection range
GetSelection(&selStart, &selEnd);
// Determine which character was clicked on
QDToFrame(hitPt, &framePt);
charOffset = GetCharOffset(&framePt);
origCharOffset = lastCharOffset = charOffset;
// Determine the new selection range based on the number of clicks
if (gClicks == 1) {
// If the Shift key is down, extend the selection
if (modifierKeys & shiftKey)
origCharOffset = lastCharOffset = itsSelAnchor;
else
SetSelection(charOffset, charOffset, kRedraw);
}
else if (gClicks == 2) {
GetWordBounds(charOffset, &selStart, &selEnd);
SetSelection(selStart, selEnd, kRedraw);
}
else {
GetParagraphBounds(charOffset, &selStart, &selEnd);
if ((itsSelStart != itsSelEnd) && (selStart < itsSelStart) && (selEnd > itsSelEnd)) {
HiliteTextRange(selStart, itsSelStart);
HiliteTextRange(itsSelEnd, selEnd);
SetSelection(selStart, selEnd, kNoRedraw);
}
else if ((selStart != itsSelStart) || (selEnd != itsSelEnd))
SetSelection(selStart, selEnd, kRedraw);
}
// Ensure that the insertion caret is visible
if (selStart == selEnd)
ShowCaret();
// Track the selection while the mouse is down
while (StillDown()) {
// Get the current mouse position in frame coordinates
GetMouse(&hitPt);
QDToFrame(hitPt, &framePt);
autoScrollPt = framePt;
PinInRect(&frame, &framePt);
// Check if the mouse has moved to a new character
charOffset = GetCharOffset(&framePt);
if (charOffset != lastCharOffset) {
// Remember the current selection range
selStart = itsSelStart;
selEnd = itsSelEnd;
// Determine the new selection range
if (gClicks == 1) {
newSelStart = Min(origCharOffset, charOffset);
newSelEnd = Max(origCharOffset, charOffset);
}
else if (gClicks == 2) {
if (charOffset < origCharOffset) {
newSelStart = WordBreakHook(charOffset, kBreakLeft);
newSelEnd = WordBreakHook(origCharOffset, kBreakRight);
}
else {
newSelStart = WordBreakHook(origCharOffset, kBreakLeft);
newSelEnd = WordBreakHook(charOffset, kBreakRight);
}
}
else { // gClicks == 3
if (charOffset < origCharOffset) {
GetParagraphBounds(origCharOffset, NULL, &newSelEnd);
GetParagraphBounds(charOffset, &newSelStart, NULL);
}
else {
GetParagraphBounds(origCharOffset, &newSelStart, NULL);
GetParagraphBounds(charOffset, NULL, &newSelEnd);
}
}
// Adjust the hilited text
if ((selStart == selEnd) && fCaretVisible && ((selStart != newSelStart) || (selStart != newSelEnd)))
HideCaret();
if (selStart != newSelStart)
HiliteTextRange(Min(selStart, newSelStart), Max(selStart, newSelStart));
if (selEnd != newSelEnd)
HiliteTextRange(Min(selEnd, newSelEnd), Max(selEnd, newSelEnd));
SetSelection(newSelStart, newSelEnd, kNoRedraw);
if (newSelStart == newSelEnd)
ShowCaret();
// Remember the current character offset
lastCharOffset = charOffset;
}
// Scroll the text automatically while the user is selecting text
AutoScroll(&autoScrollPt);
}
// Determine the anchor point for the selection
if (gClicks == 1)
itsSelAnchor = origCharOffset;
else if (gClicks == 2)
itsSelAnchor = WordBreakHook(origCharOffset, charOffset < origCharOffset ? kBreakRight : kBreakLeft);
else {
GetParagraphBounds(origCharOffset, &selStart, &selEnd);
itsSelAnchor = (charOffset < origCharOffset ? selEnd : selStart);
}
// For non-editable but selectable text, we want to display a selection
// range so the user can copy, but we don't want to display a caret
if (!this->editable && (itsSelStart == itsSelEnd))
HideCaret();
// Notify our dependents that the selection has changed
SelectionChanged();
}
/******************************************************************************
HitSamePart {OVERRIDE}
Check whether two points hit the same character in the text.
The points are in Window coordinates.
******************************************************************************/
Boolean CPEditText::HitSamePart(Point pointA, Point pointB)
{
LongPt framePtA, framePtB;
WindToFrame(pointA, &framePtA);
WindToFrame(pointB, &framePtB);
return (GetCharOffset(&framePtA) == GetCharOffset(&framePtB));
}
/******************************************************************************
DoKeyDown {OVERRIDE}
Respond to a keystroke.
******************************************************************************/
void CPEditText::DoKeyDown(char theChar, Byte keyCode, EventRecord *macEvent)
{
if (IsArrowKey(theChar) && (macEvent->modifiers & cmdKey)) {
DoArrowKey(theChar, macEvent->modifiers);
// Notify our dependents that the selection has changed
// This used to be done in DoArrowKey, but we now do it here
// so the CTextEditTask::DoFwdDelete works properly
SelectionChanged();
}
else
inherited::DoKeyDown(theChar, keyCode, macEvent);
}
/******************************************************************************
TypeChar {OVERRIDE}
Process a single keystroke. All undo setup has already been
performed, and the keystroke should be handled directly.
******************************************************************************/
void CPEditText::TypeChar(char theChar, short theModifiers)
{
// Need to check for arrow keys here, even though we check in the
// DoKeyDown method, since CTextEditTask calls TypeChar directly
if (IsArrowKey(theChar))
DoArrowKey(theChar, theModifiers);
else {
long selStart = itsSelStart;
long selEnd = itsSelEnd;
// Obscure the cursor so it is hidden until the mouse is moved
ObscureCursor();
// Now perform the typing
if (theChar == kBackspace) {
if (selStart == selEnd && selStart > 0) {
HideCaret();
DeleteTextRange(selStart - 1, selStart, kRedraw);
ShowCaret();
}
else
DeleteTextRange(selStart, selEnd, kRedraw);
}
else {
if (selStart != selEnd)
ReplaceSelection(&theChar, sizeof(char));
else
InsertTextPtr(&theChar, sizeof(char), kNoRedraw);
}
// Ensure that the insertion point is visible
ScrollToOffset(itsSelStart);
}
}
/******************************************************************************
DoArrowKey
Handle a cursor key. The behavior is as follows:
modifier key
<none> Option Command
up prev line prev page first line
down next line next page last line
left prev char prev word line start
right next char next word line end
******************************************************************************/
void CPEditText::DoArrowKey(char theChar, short theModifiers)
{
Boolean commandKeyDown = ((theModifiers & cmdKey) != 0);
Boolean optionKeyDown = ((theModifiers & optionKey) != 0);
Boolean shiftKeyDown = ((theModifiers & shiftKey) != 0);
Boolean isInsertion;
Boolean isUpDownArrow;
register long nonAnchor;
register long selChar;
long numLines;
long selLine;
long selStart;
long selEnd;
long textLength;
long newStart;
long newEnd;
LongPt charPt;
GetSelection(&selStart, &selEnd);
textLength = GetLength();
numLines = GetNumLines();
// Determine if we have an insertion point or not
isInsertion = (selStart == selEnd);
// Reset the selection anchor position if necessary
if (isInsertion)
itsSelAnchor = selStart;
else if (itsSelAnchor < 0) {
if ((theChar == kLeftCursor) || (theChar == kUpCursor))
itsSelAnchor = selEnd;
else
itsSelAnchor = selStart;
}
// Determine the non-anchored selection position
nonAnchor = (itsSelAnchor == selEnd ? selStart : selEnd);
// Now handle the keystroke
if ((theChar == kLeftCursor) || (theChar == kRightCursor)) {
if (commandKeyDown) {
selLine = FindLine(nonAnchor);
if (theChar == kLeftCursor)
selChar = GetLineStart(selLine);
else {
selChar = GetLineEnd(selLine);
if (selLine < numLines - 1)
--selChar;
}
}
else if (optionKeyDown) {
Boolean expanding;
if (shiftKeyDown) {
if (theChar == kLeftCursor)
expanding = (nonAnchor <= itsSelAnchor);
else
expanding = (nonAnchor >= itsSelAnchor);
}
else
expanding = TRUE;
selChar = nonAnchor;
if (!expanding) {
if (theChar == kLeftCursor) {
newEnd = selChar;
while ((selChar > itsSelAnchor) && ((!GetWordBounds(selChar, &newStart, &newEnd)) || (newEnd + 1 >= nonAnchor)))
selChar = newStart - 1;
if (selChar <= itsSelAnchor) {
expanding = TRUE;
selChar = itsSelAnchor;
}
else
selChar = newEnd;
}
else {
newStart = selChar;
while ((selChar < itsSelAnchor) && ((!GetWordBounds(selChar, &newStart, &newEnd)) || (newStart <= nonAnchor)))
selChar = newEnd + 1;
if (selChar >= itsSelAnchor) {
expanding = TRUE;
selChar = itsSelAnchor;
}
}
}
if (expanding) {
if (theChar == kLeftCursor) {
long minSelChar = selChar;
newStart = selChar;
while ((selChar > 0) && ((!GetWordBounds(selChar, &newStart, &newEnd)) || (newStart >= minSelChar)))
--selChar;
selChar = Min(selChar, newStart);
}
else {
long maxSelChar = textLength - 1;
newEnd = selChar;
while ((selChar < maxSelChar) && !GetWordBounds(selChar + 1, &newStart, &newEnd))
++selChar;
selChar = Max(selChar, newEnd);
}
}
}
else if (shiftKeyDown || isInsertion) {
if (theChar == kLeftCursor)
selChar = nonAnchor - 1;
else
selChar = nonAnchor + 1;
selChar = Min(Max(selChar, 0), textLength);
}
else
selChar = ((theChar == kLeftCursor) ? selStart : selEnd);
isUpDownArrow = FALSE;
}
else { // ((theChar == kUpCursor) || (theChar == kDownCursor))
selLine = FindLine(nonAnchor);
if ((theChar == kUpCursor) && (commandKeyDown || (selLine == 0))) {
selChar = 0;
isUpDownArrow = FALSE;
}
else if ((theChar == kDownCursor) && (commandKeyDown || (selLine == numLines - 1))) {
selChar = textLength;
isUpDownArrow = FALSE;
}
else {
if (!fUpDownArrow) {
GetCharPoint(nonAnchor, &charPt);
itsUpDownHOffset = charPt.h;
}
if (optionKeyDown) {
short hSpan, vSpan;
GetFrameSpan(&hSpan, &vSpan);
if (theChar == kUpCursor)
selLine -= vSpan;
else
selLine += vSpan;
}
else {
if (theChar == kUpCursor)
--selLine;
else
++selLine;
}
GetCharPoint(GetLineStart(selLine), &charPt);
charPt.h = (long)itsUpDownHOffset;
selChar = GetCharOffset(&charPt);
isUpDownArrow = TRUE;
}
}
// Set the new selection, extending it if the Shift key was down
if (!shiftKeyDown)
SetSelection(selChar, selChar, kRedraw);
else {
newStart = Min(selChar, itsSelAnchor);
newEnd = Max(selChar, itsSelAnchor);
HideCaret();
if ((selStart == selEnd) || (newStart == newEnd))
SetSelection(newStart, newEnd, kRedraw);
else {
if (newStart != selStart)
HiliteTextRange(Min(newStart, selStart), Max(newStart, selStart));
if (newEnd != selEnd)
HiliteTextRange(Min(newEnd, selEnd), Max(newEnd, selEnd));
SetSelection(newStart, newEnd, kNoRedraw);
}
}
// Make sure the selected character is visible within the frame
ScrollToOffset(selChar);
// Remember if this character was an up or down arrow
fUpDownArrow = isUpDownArrow;
}
/**** T E X T S P E C I F I C A T I O N M E T H O D S ****/
/******************************************************************************
SetTextPtr {OVERRIDE}
Replace all the text with the text in the given buffer.
******************************************************************************/
void CPEditText::SetTextPtr(Ptr textPtr, long numChars)
{
long prevSelStart = itsSelStart;
long prevSelEnd = itsSelEnd;
// Check that enough memory is available for the operation to succeed
TRY {
SetSelection(0, itsTextLength, kNoRedraw);
CheckInsertion(textPtr, numChars, kUseSelection, NULL, NULL);
}
CATCH {
SetSelection(prevSelStart, prevSelEnd, kNoRedraw);
}
ENDTRY
// Resize the text handle and copy the contents of the given buffer
// into it, then recalculate the line starts and bounds rectangle
#if qPEUseInsertionGap
CloseGap();
#endif
SetHandleSize(itsTextHandle, numChars);
FailMemError();
BlockMove(textPtr, *itsTextHandle, numChars);
itsTextLength = numChars;
itsSelStart = itsSelEnd = 0;
CalcLineStarts();
Refresh();
}
/******************************************************************************
UseTextHandle
Replace all the text with the contents of a given handle. Unlike
SetTextHandle, this method does not copy the contents of the handle,
so the caller must not dispose of the handle.
******************************************************************************/
void CPEditText::UseTextHandle(Handle textHandle)
{
ASSERT(textHandle != NULL);
if (textHandle != itsTextHandle) {
ForgetHandle(itsTextHandle);
itsTextHandle = textHandle;
}
itsTextLength = GetHandleSize(textHandle);
itsSelStart = itsSelEnd = 0;
#if qPEUseInsertionGap
itsGapPosition = itsGapLength = 0;
#endif
CalcLineStarts();
Refresh();
}
/******************************************************************************
InsertTextPtr {OVERRIDE}
Insert a copy of the given text at the start of the selection.
******************************************************************************/
void CPEditText::InsertTextPtr(Ptr insertPtr, long insertLen, Boolean redraw)
{
long numInsertCRs;
// Check that enough memory is available for the insertion to succeed
// As a side effect, CheckInsertion also counts the number of carriage
// returns in the text being inserted
CheckInsertion(insertPtr, insertLen, kDontUseSelection, &numInsertCRs, NULL);
// Call the internal method InsertText to insert the text
InsertText(insertPtr, insertLen, numInsertCRs, redraw);
}
/******************************************************************************
CopyTextRange {OVERRIDE}
Return a handle to a copy of the given range of text.
******************************************************************************/
Handle CPEditText::CopyTextRange(long start, long end)
{
Handle copyHandle;
long length;
// Determine the length of text to copy
end = Min(end, itsTextLength);
length = Max(end - start, 0);
// Allocate a handle for the copied text
copyHandle = NewHandleCanFail(length);
FailNIL(copyHandle);
// Copy the text from our buffer to the handle just allocated
if (length > 0) {
#if qPEUseInsertionGap
Ptr textP = *itsTextHandle;
long gapPosition = itsGapPosition;
long gapLength = itsGapLength;
if (end <= gapPosition)
BlockMove(textP + start, *copyHandle, length);
else if (start >= gapPosition)
BlockMove(textP + start + gapLength, *copyHandle, length);
else {
long lengthBeforeGap = gapPosition - start;
BlockMove(textP + start, *copyHandle, lengthBeforeGap);
BlockMove(textP + start + gapLength + lengthBeforeGap, *copyHandle + lengthBeforeGap, length - lengthBeforeGap);
}
#else
BlockMove(*itsTextHandle + start, *copyHandle, length);
#endif
}
return copyHandle;
}
/******************************************************************************
DeleteTextRange
Delete the given range of text.
******************************************************************************/
void CPEditText::DeleteTextRange(long start, long end, Boolean redraw)
{
DeleteText(start, end, CountRangeCRs(start, end), redraw);
}
/******************************************************************************
ReplaceTextRange
Replace a range of characters with the given text.
******************************************************************************/
void CPEditText::ReplaceTextRange(long start, long end, Ptr replacePtr, long replaceLen)
{
HideSelection(kHideSelection, kRedraw);
SetSelection(start, end, kNoRedraw);
ReplaceSelection(replacePtr, replaceLen);
}
/******************************************************************************
ReplaceSelection
Replace the current selection with the given text. If replacePtr
is NULL, just delete the current selection.
******************************************************************************/
void CPEditText::ReplaceSelection(Ptr replacePtr, long replaceLen)
{
Boolean refresh;
long selStart;
long selEnd;
long numReplaceCRs;
long numInsertCRs;
long numLinesDelta;
// Check that enough memory is available for the replacement to succeed
// As a side effect, CheckInsertion also counts the number of carriage
// returns in the text being inserted and in the current selection
CheckInsertion(replacePtr, replaceLen, kUseSelection, &numInsertCRs, &numReplaceCRs);
// Delete the current selection, if any
GetSelection(&selStart, &selEnd);
if (selStart < selEnd) {
DeleteText(selStart, selEnd, numReplaceCRs, kNoRedraw);
numLinesDelta = -numReplaceCRs;
refresh = TRUE;
}
else {
numLinesDelta = 0;
refresh = FALSE;
}
// Insert the new text, if any
if ((replacePtr != NULL) && (replaceLen > 0)) {
InsertText(replacePtr, replaceLen, numInsertCRs, kNoRedraw);
SetSelection(selStart + replaceLen, selStart + replaceLen, kNoRedraw);
refresh = (numLinesDelta != 0);
}
// Refresh the text if necessary
if (refresh)
RefreshTextAfter(selStart, (numLinesDelta == 0));
}
/******************************************************************************
PerformEditCommand {OVERRIDE}
Perform the standard cut, copy, paste, and clear commands on the text.
******************************************************************************/
void CPEditText::PerformEditCommand(long theCommand)
{
Handle textH;
long selStart;
long selEnd;
Prepare();
GetSelection(&selStart, &selEnd);
// Copy the selection range to the Clipboard for Cut or Copy
if (((theCommand == cmdCut) || (theCommand == cmdCopy)) && (selStart != selEnd)) {
textH = CopyTextRange(selStart, selEnd);
TRY {
gClipboard->EmptyScrap();
gClipboard->PutData('TEXT', textH);
DisposHandle(textH);
}
CATCH {
ForgetHandle(textH);
}
ENDTRY
}
// Delete the selection range for Cut or Clear
if (((theCommand == cmdCut) || (theCommand == cmdClear)) && (selStart < selEnd))
DeleteTextRange(selStart, selEnd, kRedraw);
// Replace the current selection with the contents of the Clipboard for Paste
else if ((theCommand == cmdPaste) && gClipboard->GetData('TEXT', &textH)) {
HLockHi(textH);
TRY {
ReplaceSelection(*textH, GetHandleSize(textH));
DisposHandle(textH);
}
CATCH {
ForgetHandle(textH);
}
ENDTRY
}
// Ensure that the insertion caret is visible
ScrollToOffset(itsSelStart);
}
/**** T E X T C H A R A C T E R I S T I C S M E T H O D S ****/
/******************************************************************************
SetFontNumber {OVERRIDE}
Specify the font for text by font number.
******************************************************************************/
void CPEditText::SetFontNumber(short aFontNumber)
{
TextInfoRec textInfo;
itsTextFont = aFontNumber;
((CTextEnvirons *)itsEnvironment)->GetTextInfo(&textInfo);
textInfo.fontNumber = aFontNumber;
((CTextEnvirons *)itsEnvironment)->SetTextInfo(&textInfo);
CalcLineHeight();
}
/******************************************************************************
SetFontSize {OVERRIDE}
Specify the point size of text.
******************************************************************************/
void CPEditText::SetFontSize(short aSize)
{
TextInfoRec textInfo;
itsTextSize = aSize;
((CTextEnvirons *)itsEnvironment)->GetTextInfo(&textInfo);
textInfo.theSize = aSize;
((CTextEnvirons *)itsEnvironment)->SetTextInfo(&textInfo);
CalcLineHeight();
}
/******************************************************************************
SetFontStyle {OVERRIDE}
Specify the style, such as bold or italic, for text.
******************************************************************************/
void CPEditText::SetFontStyle(short aStyle)
{
TextInfoRec textInfo;
if (aStyle == normal) // Plain text is the absence of any style
itsTextFace = normal;
else
itsTextFace ^= aStyle; // Toggle style characteristic by XOR'ing proper bit
((CTextEnvirons *)itsEnvironment)->GetTextInfo(&textInfo);
textInfo.theStyle = itsTextFace;
((CTextEnvirons *)itsEnvironment)->SetTextInfo(&textInfo);
CalcLineHeight();
}
/******************************************************************************
SetTextMode {OVERRIDE}
Specify the transfer mode used for drawing text.
******************************************************************************/
void CPEditText::SetTextMode(short aMode)
{
TextInfoRec textInfo;
itsTextMode = aMode;
((CTextEnvirons *)itsEnvironment)->GetTextInfo(&textInfo);
textInfo.theMode = aMode;
((CTextEnvirons *)itsEnvironment)->SetTextInfo(&textInfo);
Refresh();
}
/******************************************************************************
SetAlignCmd {OVERRIDE}
Specify the alignment for text.
******************************************************************************/
void CPEditText::SetAlignCmd(long anAlignCmd)
{
// Null method -- alignment not supported by CPEditText
}
/******************************************************************************
GetAlignCmd {OVERRIDE}
Return the current alignment.
******************************************************************************/
long CPEditText::GetAlignCmd()
{
return cmdAlignLeft;
}
/******************************************************************************
SetSpacingCmd {OVERRIDE}
Specify the vertical spacing between lines of text.
******************************************************************************/
void CPEditText::SetSpacingCmd(long aSpacingCmd)
{
itsSpacingCmd = aSpacingCmd;
CalcLineHeight();
}
/******************************************************************************
GetSpacingCmd {OVERRIDE}
Return the vertical spacing between lines of text.
******************************************************************************/
long CPEditText::GetSpacingCmd()
{
return itsSpacingCmd;
}
#if !qTCL113
/******************************************************************************
SetHorizontalScroll
Enable/disable automatic horizontal scrolling. If TRUE, typing
and editing can cause automatic horizontal scrolling to keep the
selection range or insertion point in view. (Automatic vertical
scrolling is always enabled.) If FALSE, the insertion point is
allowed to travel out of sight.
(This method is provided in CAbstractText with TCL 1.1.3, so
it is only necessary when compiling for TCL 1.1.2 or earlier.)
******************************************************************************/
void CPEditText::SetHorizontalScroll(Boolean doHoriz)
{
scrollHoriz = doHoriz;
}
#endif // !qTCL113
/******************************************************************************
SetTabSpaces
Set the number of spaces per tab.
******************************************************************************/
void CPEditText::SetTabSpaces(short tabSpaces)
{
itsTabSpaces = Max(tabSpaces, 1);
CalcTabWidth();
Refresh();
}
/******************************************************************************
GetTabSpaces
Return the number of spaces per tab.
******************************************************************************/
short CPEditText::GetTabSpaces()
{
return itsTabSpaces;
}
/******************************************************************************
SetOutlineHiliting
Set whether or not to outline an inactive selection range and
to draw the inactive insertion caret in gray.
******************************************************************************/
void CPEditText::SetOutlineHiliting(Boolean outlineHilite)
{
fOutlineHilite = outlineHilite;
if (!fReallyActive)
Refresh();
}
/******************************************************************************
SetItalicCaret
Set whether or not to use a slanted cursor for italic text.
******************************************************************************/
void CPEditText::SetItalicCaret(Boolean useItalicCaret)
{
HideSelection(kHideSelection, kRedraw);
fUseItalicCaret = useItalicCaret;
HideSelection(kUnhideSelection, kRedraw);
}
/******************************************************************************
SetShowInvisibles
Set whether or not to show invisible characters.
******************************************************************************/
void CPEditText::SetShowInvisibles(Boolean showInvisibles)
{
fShowInvisibles = showInvisibles;
Refresh();
}
/******************************************************************************
GetShowInvisibles
Return whether or not invisible characters are shown.
******************************************************************************/
Boolean CPEditText::GetShowInvisibles()
{
return fShowInvisibles;
}
/******************************************************************************
GetHeight {OVERRIDE}
Return the height of the indicated lines of text.
******************************************************************************/
long CPEditText::GetHeight(long startLine, long endLine)
{
return (itsLineHeight * (endLine - startLine + 1));
}
/******************************************************************************
GetCharOffset {OVERRIDE}
Return the offset into the text buffer of the character position
at a point in Frame coordinates. The offset does not reflect
the position or length of the insertion gap.
******************************************************************************/
long CPEditText::GetCharOffset(LongPt *aPt)
{
long line;
register short offset;
register short lineLen;
register short width;
register short lastWidth;
register short horiz;
register short *widthsP;
ShortHandle widthsH;
Prepare();
// Determine which line the point lies on
line = (aPt->v - VertInset()) / itsLineHeight;
// Check for and handle special cases above the first line and below the last line
if (line < 0)
return 0;
else if (line > itsNumLines - 1)
return itsTextLength;
// Get the number of characters in the line
lineLen = GetLineLength(line);
// Check if the horizontal coordinate is smaller than the horizontal inset of the pane
horiz = aPt->h - HorizInset();
if (horiz <= 0)
offset = 0;
else {
// Calculate character pixel offsets for the given line
widthsH = MeasureLineWidths(line);
widthsP = *widthsH;
offset = 0;
lastWidth = 0;
while ((offset < lineLen) && ((width = *++widthsP) < horiz)) {
lastWidth = width;
++offset;
}
DisposHandle((Handle)widthsH);
// Bump the offset if the point is on the right side of a character
if (horiz >= (width + lastWidth) / 2)
++offset;
// Set the offset to the last character on the line if the point
// is greater than the pixel length of the line
if (offset > lineLen || ((offset == lineLen) && (GetChar(offset) == kReturn)))
offset = (line < itsNumLines - 1 ? lineLen - 1 : lineLen);
}
return ((*itsLineStarts)[line] + offset);
}
/******************************************************************************
GetCharPoint {OVERRIDE}
Return the Frame coordinates of the character at the given offset
in the text buffer. The offset should not take into account the
position or length of the insertion gap.
******************************************************************************/
void CPEditText::GetCharPoint(long charOffset, LongPt *aPt)
{
long line;
ShortHandle widthsH;
// Ensure that the offset is within bounds
charOffset = Max(charOffset, 0);
charOffset = Min(charOffset, itsTextLength);
// Determine which line the given character offset is on
// and calculate the pixel widths for that line
line = FindLine(charOffset);
widthsH = MeasureLineWidths(line);
// Compute the pixel coordinates for the character
aPt->v = VertInset() + line * itsLineHeight + itsFontAscent;
aPt->h = HorizInset() + (*widthsH)[charOffset - (*itsLineStarts)[line]];
// Dispose of the pixel widths handle
DisposHandle((Handle)widthsH);
}
/******************************************************************************
GetTextStyle {OVERRIDE}
Return current style information. whichAttributes is a set of
flags indicating which text attributes are requested, and upon
return, indicates which of the requested attributes are valid
in the TextStyle record.
******************************************************************************/
void CPEditText::GetTextStyle(short *whichAttributes, TextStyle *aStyle)
{
if (*whichAttributes & doFont)
aStyle->tsFont = itsTextFont;
if (*whichAttributes & doFace)
aStyle->tsFace = itsTextFace;
if (*whichAttributes & doSize)
aStyle->tsSize = itsTextSize;
*whichAttributes &= ~doColor;
}
/******************************************************************************
GetCharStyle {OVERRIDE}
Return style information about the character at the given offset.
Since a PEditText pane only supports a single font/size/style
for the text, this method returns information about the global
text characteristics in the TextStyle record.
******************************************************************************/
void CPEditText::GetCharStyle(long charOffset, TextStyle *aStyle)
{
aStyle->tsFont = itsTextFont;
aStyle->tsFace = itsTextFace;
aStyle->tsSize = itsTextSize;
aStyle->tsColor.red = aStyle->tsColor.green = aStyle->tsColor.blue = 0;
}
/**** A C C E S S I N G M E T H O D S ****/
/******************************************************************************
SetBounds {OVERRIDE}
Set the bounds of the PEditText panorama. The text is automatically
scrolled if necessary to fill the entire frame.
******************************************************************************/
void CPEditText::SetBounds(LongRect *aBounds)
{
long hDelta;
long vDelta;
// Scroll the text if necessary to fill the entire frame
if (aBounds->right * hScale < frame.right)
hDelta = Min(frame.left - aBounds->left * hScale, frame.right - aBounds->right * hScale);
else
hDelta = 0;
if (aBounds->bottom * vScale < frame.bottom)
vDelta = Min(frame.top - aBounds->top * vScale, frame.bottom - aBounds->bottom * vScale);
else
vDelta = 0;
if ((hDelta != 0) || (vDelta != 0))
Scroll(-hDelta / hScale, -vDelta / vScale, kRedraw);
// Call the inherited method to update the bounds instance variable
// and adjust the maximum values of the scroll bars
inherited::SetBounds(aBounds);
}
#if qTCL113
/******************************************************************************
GetSteps {OVERRIDE}
Return the horizontal and vertical number of units to scroll
in a single step.
******************************************************************************/
void CPEditText::GetSteps(short *hStep, short *vStep)
{
inherited::GetSteps(hStep, vStep);
if ((*hStep == 1) && (hScale == 1))
*hStep = itsMaxCharWidth;
}
#endif // qTCL113
/******************************************************************************
GetTextHandle {OVERRIDE}
Return a handle to the text buffer. This method closes the
insertion gap before returning the text handle, which can have
a negative impact on performance. Clients who frequently call
this method should considering calling GetRawTextHandle instead,
although doing so will require taking into account the position
and length of the gap.
******************************************************************************/
Handle CPEditText::GetTextHandle()
{
#if qPEUseInsertionGap
CloseGap();
#endif
return itsTextHandle;
}
#if qPEUseInsertionGap
/******************************************************************************
GetRawTextHandle
Return a handle to the text buffer. Clients should generally
use GetTextHandle instead of GetRawTextHandle, unless they are
willing to contend with the insertion gap.
******************************************************************************/
Handle CPEditText::GetRawTextHandle()
{
return itsTextHandle;
}
#endif // qPEUseInsertionGap
/******************************************************************************
FindLine {OVERRIDE}
Return the line number containing the specified character position.
Both lines and character positions are numbered starting from zero.
If the character position is before the start of the text (i.e., a
negative number), a value of zero is returned; if it is beyond the
end of the text, the number of the last line is returned.
******************************************************************************/
long CPEditText::FindLine(register long charPos)
{
register long line, lineLow, lineHigh;
register long lineStart;
register long *lineStarts = *itsLineStarts;
long numLines = itsNumLines;
// Check if it's in the first line
if ((numLines == 1) || (charPos < lineStarts[1]))
return 0;
// Check if it's in the last line
if (charPos >= lineStarts[numLines - 1])
return numLines - 1;
// Perform a binary search through itsLineStarts
lineLow = 0;
lineHigh = numLines;
while (lineHigh >= lineLow) {
line = (lineLow + lineHigh) >> 1;
lineStart = lineStarts[line];
if (charPos == lineStart)
break;
else if (charPos > lineStart)
lineLow = line + 1;
else
lineHigh = line - 1;
}
return ((charPos < lineStart) ? line - 1 : line);
}
/******************************************************************************
GetLineStart
Return the offset of the character at the start of the given line.
The line number parameter is zero-based. The offset does not
reflect the position or length of the insertion gap.
******************************************************************************/
long CPEditText::GetLineStart(long line)
{
if (line <= 0)
return 0;
else if (line >= itsNumLines)
return itsTextLength;
else
return (*itsLineStarts)[line];
}
/******************************************************************************
GetLineEnd
Return the offset of the character at the end of the given line.
The line number parameter is zero-based. The offset does not
reflect the position or length of the insertion gap.
******************************************************************************/
long CPEditText::GetLineEnd(long line)
{
if (line < 0)
return 0;
else if (line >= itsNumLines - 1)
return itsTextLength;
else
return (*itsLineStarts)[line + 1];
}
/******************************************************************************
GetLineLength
Return the number of characters in the given line. The length does
not reflect the length of the insertion gap.
******************************************************************************/
short CPEditText::GetLineLength(long line)
{
long lineStart;
long lineEnd;
short lineLength;
if ((line < 0) || (line >= itsNumLines))
lineLength = 0;
else {
lineStart = (*itsLineStarts)[line];
lineEnd = (line < itsNumLines - 1 ? (*itsLineStarts)[line + 1] : itsTextLength);
lineLength = lineEnd - lineStart;
}
return lineLength;
}
/******************************************************************************
GetLength {OVERRIDE}
Return the number of characters in the text buffer. The length does
not reflect the length of the insertion gap.
******************************************************************************/
long CPEditText::GetLength()
{
return itsTextLength;
}
/******************************************************************************
GetNumLines {OVERRIDE}
Return the number of lines of text.
******************************************************************************/
long CPEditText::GetNumLines()
{
return itsNumLines;
}
/******************************************************************************
GetSelection {OVERRIDE}
Return the start and end of the selection. The positions do not
reflect the position or length of the insertion gap.
******************************************************************************/
void CPEditText::GetSelection(long *selStart, long *selEnd)
{
*selStart = itsSelStart;
*selEnd = itsSelEnd;
}
/******************************************************************************
GetChar
Return the character at the given position.
******************************************************************************/
short CPEditText::GetChar(long aPosition)
{
#if qPEUseInsertionGap
Ptr textP = *itsTextHandle;
if (aPosition >= itsGapPosition)
textP += itsGapLength;
return (unsigned char)textP[aPosition];
#else
return (unsigned char)(*itsTextHandle)[aPosition];
#endif
}
/******************************************************************************
GetCharBefore {OVERRIDE}
Return the character before aPosition. The character and its size
are returned in charBuf, and aPosition is updated with the starting
position of the character. If there is no preceding character, then
Length(charBuf) is 0, and aPosition is not updated.
******************************************************************************/
void CPEditText::GetCharBefore(long *aPosition, tCharBuf charBuf)
{
long charPos = *aPosition;
if ((charPos > 0) && (charPos <= itsTextLength)) {
charPos--;
Length(charBuf) = 1;
#if qPEUseInsertionGap
charBuf[1] = (*itsTextHandle)[charPos < itsGapPosition ? charPos : charPos + itsGapLength];
#else
charBuf[1] = (*itsTextHandle)[charPos];
#endif
*aPosition = charPos;
}
else
Length(charBuf) = 0;
}
/******************************************************************************
GetCharAfter {OVERRIDE}
Return the character after aPosition. The character and its size
are returned in charBuf, and aPosition is updated with the starting
position of the character. If there is no following character, then
Length(charBuf) is 0, and aPosition is not updated.
******************************************************************************/
void CPEditText::GetCharAfter(long *aPosition, tCharBuf charBuf)
{
long charPos = *aPosition;
if ((charPos >= 0) && (charPos < itsTextLength)) {
Length(charBuf) = 1;
#if qPEUseInsertionGap
charBuf[1] = (*itsTextHandle)[charPos < itsGapPosition ? charPos : charPos + itsGapLength];
#else
charBuf[1] = (*itsTextHandle)[charPos];
#endif
}
else
Length(charBuf) = 0;
}
/******************************************************************************
GetWordBounds
Find the starting and ending offsets of the word containing
the given character offset. Returns TRUE if this character is part
of a word, FALSE otherwise.
******************************************************************************/
Boolean CPEditText::GetWordBounds(long charPos, long *wordStart, long *wordEnd)
{
ASSERT(wordStart != NULL);
ASSERT(wordEnd != NULL);
if ((charPos < 0) || (charPos >= itsTextLength))
return FALSE;
else {
*wordStart = WordBreakHook(charPos, kBreakLeft);
*wordEnd = WordBreakHook(charPos, kBreakRight);
return (*wordStart < *wordEnd);
}
}
/******************************************************************************
GetParagraphBounds
Find the starting and ending offsets of the paragraph containing
the given character offset. Paragraphs are defined as a sequence
of characters delineated by carriage returns or the beginning/end
of the text.
******************************************************************************/
void CPEditText::GetParagraphBounds(long charPos, long *paraStart, long *paraEnd)
{
if (paraStart != NULL) {
register long start = charPos;
while (--start >= 0 && GetChar(start) != kReturn)
;
*paraStart = ++start;
}
if (paraEnd != NULL) {
register long end = charPos;
while (end < itsTextLength && GetChar(end++) != kReturn)
;
*paraEnd = end;
}
}
#if qPEUseInsertionGap
/******************************************************************************
GetGapPosition
Return the position of the insertion gap. Clients will generally
not need to use this method, as they should not need to know about
the position or length of the gap.
******************************************************************************/
long CPEditText::GetGapPosition()
{
return itsGapPosition;
}
/******************************************************************************
GetGapLength
Return the length of the insertion gap. Clients will generally
not need to use this method, as they should not need to know about
the position or length of the gap.
******************************************************************************/
long CPEditText::GetGapLength()
{
return itsGapLength;
}
#endif // qPEUseInsertionGap
/**** H O O K M E T H O D S ****/
/******************************************************************************
WordBreakHook
Return the character offset at which the next word break in the
specified direction should occur. Override if a different work
break criterion is desired.
******************************************************************************/
#define TestBit(p,b) (((long *)(p))[(b) >> 5] & (0x80000000U >> ((b) & 0x1F)))
long CPEditText::WordBreakHook(register long charPos, BreakDirection direction)
{
register Byte c;
register long textLength;
register Ptr textP = *itsTextHandle;
#if qPEUseInsertionGap
register long gapLength = itsGapLength;
long gapPosition = itsGapPosition;
register Ptr gapP = textP + gapPosition;
#endif
static long wordBreaks[8] = { // Packed boolean flags to indicate
0x00000000, 0x0000FFC0, // whether or not a character is a
0x7FFFFFE1, 0x7FFFFFE0, // word break character
0x00000000, 0x00000000,
0x00000000, 0x00000000
};
textP += charPos;
#if qPEUseInsertionGap
if (charPos >= gapPosition)
textP += gapLength;
#endif
if (direction == kBreakLeft) {
#if qPEUseInsertionGap
gapP += gapLength;
#endif
do {
#if qPEUseInsertionGap
if (textP == gapP)
textP -= gapLength;
#endif
} while (charPos-- && (c = *--textP, TestBit(wordBreaks, c)));
return charPos + 1;
}
else {
textLength = itsTextLength;
do {
#if qPEUseInsertionGap
if (textP == gapP)
textP += gapLength;
#endif
} while (charPos < textLength && (c = *textP++, TestBit(wordBreaks, c)) && ++charPos);
return charPos;
}
}
/******************************************************************************
CaretHook
Draw the text insertion caret. The rectangle is in QuickDraw
coordinates. Override if a different caret appearance is desired.
******************************************************************************/
void CPEditText::CaretHook(const Rect *caretRect)
{
PenState prevPenState;
Boolean canDrawInRealGray = FALSE;
RGBColor grayColor;
RGBColor prevForeColor;
GetPenState(&prevPenState);
PenMode(patXor);
// Draw the caret in gray if the pane isn't active
if (!fReallyActive && fOutlineHilite) {
canDrawInRealGray = GetGrayRGBColor(caretRect, &grayColor, &prevForeColor);
if (canDrawInRealGray)
RGBForeColor(&grayColor);
else
PenPat(gray);
}
// Draw the caret
if ((itsTextFace & italic) && fUseItalicCaret) {
PenSize(1, 1);
MoveTo(caretRect->left, caretRect->bottom);
LineTo(caretRect->left + (caretRect->bottom - caretRect->top) / 2, caretRect->top);
}
else
PaintRect(caretRect);
SetPenState(&prevPenState);
if (canDrawInRealGray)
RGBForeColor(&prevForeColor);
}
/******************************************************************************
HiliteHook
Hilite the specified rectangle. The rectangle is in QuickDraw
coordinates. Override if a different hiliting behavior is desired.
******************************************************************************/
void CPEditText::HiliteHook(const Rect *hiliteRect)
{
if ((itsTextFace & italic) && fUseItalicCaret) {
OpenRgn();
MoveTo(hiliteRect->left, hiliteRect->bottom);
LineTo(hiliteRect->right, hiliteRect->bottom);
LineTo(hiliteRect->right + (hiliteRect->bottom - hiliteRect->top) / 2, hiliteRect->top);
LineTo(hiliteRect->left + (hiliteRect->bottom - hiliteRect->top) / 2, hiliteRect->top);
LineTo(hiliteRect->left, hiliteRect->bottom);
CloseRgn(gUtilRgn);
SetHiliteMode();
InvertRgn(gUtilRgn);
}
else {
SetHiliteMode();
InvertRect(hiliteRect);
}
}
/**** I N T E R N A L M E T H O D S ****/
#if qPEUseInsertionGap
/******************************************************************************
SetGapPosition
Internal method to set the position of the insertion gap.
******************************************************************************/
void CPEditText::SetGapPosition(long newGapPosition)
{
long gapPosition = itsGapPosition;
if ((newGapPosition != gapPosition) && (newGapPosition >= 0) && (newGapPosition <= itsTextLength)) {
Ptr textP = *itsTextHandle;
long textLength = itsTextLength;
long gapLength = itsGapLength;
if (gapLength > 0) {
if (newGapPosition > gapPosition)
BlockMove(textP + gapPosition + gapLength, textP + gapPosition, newGapPosition - gapPosition);
else
BlockMove(textP + newGapPosition, textP + newGapPosition + gapLength, gapPosition - newGapPosition);
}
itsGapPosition = newGapPosition;
}
}
/******************************************************************************
SetGapLength
Internal method to set the length of the insertion gap.
******************************************************************************/
void CPEditText::SetGapLength(long newGapLength)
{
if ((newGapLength != itsGapLength) && (newGapLength >= 0)) {
long gapLength = itsGapLength;
long gapPosition = itsGapPosition;
long textLength = itsTextLength;
Handle textHandle = itsTextHandle;
if (newGapLength < gapLength) {
BlockMove(*textHandle + gapPosition + gapLength,
*textHandle + gapPosition + newGapLength,
textLength - gapPosition);
SetHandleSize(textHandle, textLength + newGapLength);
itsGapLength = newGapLength;
}
else {
ResizeHandleCanFail(textHandle, textLength + newGapLength);
if (MemError() == noErr) {
BlockMove(*textHandle + gapPosition + gapLength,
*textHandle + gapPosition + newGapLength,
textLength - gapPosition);
itsGapLength = newGapLength;
}
}
}
}
/******************************************************************************
CloseGap
Internal method to close the insertion gap.
******************************************************************************/
void CPEditText::CloseGap()
{
SetGapLength(0);
SetGapPosition(0);
}
#endif // qPEUseInsertionGap
/******************************************************************************
CheckInsertion
Internal method to "preflight" an attempt to insert text and ensure
that enough memory is available for the insertion to succeed.
If useSelection is TRUE, the length of the selection is deducted
from the total length. If numInsertCRs is non-NULL, the number of
return characters in the text being inserted is returned. If
useSelection is TRUE and numSelectionCRs is non-NULL, the number
of return characters in the current selection is returned.
******************************************************************************/
void CPEditText::CheckInsertion(
Ptr insertPtr, // Ptr to text being inserted
long insertLen, // Length of text being inserted
Boolean useSelection, // Compensate for selection length?
long *numInsertCRs, // Number of CRs in inserted text
long *numSelectionCRs) // Number of CRs in selection
{
long prevTextLen; // Previous length of text handle
long prevLineStartsLen; // Previous length of line starts
long newTextLen; // New length of text handle
long newLineStartsLen; // New length of line starts
long insertCRsCount; // Number of CRs in inserted text
// Save the current lengths of the text and line starts handles
prevTextLen = GetHandleSize(itsTextHandle);
prevLineStartsLen = GetHandleSize((Handle)itsLineStarts);
// Count the number of return characters in the text being inserted
if ((insertPtr != NULL) && (insertLen > 0))
insertCRsCount = CountCRs(insertPtr, insertLen);
else
insertCRsCount = 0;
// Compute the new lengths of the handles
newTextLen = prevTextLen + insertLen;
newLineStartsLen = prevLineStartsLen + insertCRsCount * sizeof(long);
if (numInsertCRs != NULL)
*numInsertCRs = insertCRsCount;
// If we're taking the selection into account, adjust the new lengths
// of the handles accordingly
if (useSelection) {
long crCount = CountRangeCRs(itsSelStart, itsSelEnd);
newTextLen -= (itsSelEnd - itsSelStart);
newLineStartsLen -= crCount * sizeof(long);
if (numSelectionCRs != NULL)
*numSelectionCRs = crCount;
}
// Attempt to resize the handles
TRY {
if (newTextLen > prevTextLen) {
ResizeHandleCanFail(itsTextHandle, newTextLen);
FailMemError();
}
if (newLineStartsLen > prevLineStartsLen) {
ResizeHandleCanFail((Handle)itsLineStarts, newLineStartsLen);
FailMemError();
SetHandleSize((Handle)itsLineStarts, prevLineStartsLen);
}
if (newTextLen > prevTextLen)
SetHandleSize(itsTextHandle, prevTextLen);
}
CATCH {
SetHandleSize(itsTextHandle, prevTextLen);
SetHandleSize((Handle)itsLineStarts, prevLineStartsLen);
}
ENDTRY
}
/******************************************************************************
CountRangeCRs
Internal method to count the number of carriage return characters
in the given range of text.
******************************************************************************/
long CPEditText::CountRangeCRs(long start, long end)
{
Ptr textP = *itsTextHandle;
long length = end - start;
#if qPEUseInsertionGap
long gapPosition = itsGapPosition;
long gapLength = itsGapLength;
#endif
long crCount;
if (start >= end)
crCount = 0;
else if ((start == 0) && (end == itsTextLength))
crCount = itsNumLines - 1;
#if qPEUseInsertionGap
else if (end <= gapPosition)
crCount = CountCRs(textP + start, length);
else if (start >= gapPosition)
crCount = CountCRs(textP + start + gapLength, length);
else {
long lenBeforeGap = gapPosition - start;
crCount = CountCRs(textP + start, lenBeforeGap);
crCount += CountCRs(textP + start + gapLength + lenBeforeGap, length - lenBeforeGap);
}
#else
else
crCount = CountCRs(textP + start, length);
#endif
return crCount;
}
/******************************************************************************
InsertText
Internal method to insert a copy of the given text at the start of
the selection. Called by InsertTextPtr and ReplaceSelection.
Assumes that CheckInsertion has been called to assure that the
insertion will succeed.
******************************************************************************/
void CPEditText::InsertText(
Ptr insertPtr, // Pointer to text to insert
long insertLen, // Length of text to insert
long numInsertCRs, // Number of CRs in inserted text
Boolean redraw) // TRUE to redraw text
{
Ptr textP;
long selStart = itsSelStart;
long selEnd = itsSelEnd;
HideSelection(kHideSelection, kRedraw);
#if qPEUseInsertionGap
// Check if the text being inserted will fit into the gap
// If not, resize the text handle to accomodate the new text
if ((selStart == itsGapPosition) && (insertLen <= itsGapLength)) {
textP = *itsTextHandle;
if (insertLen == 1) // Special case to avoid BlockMove overhead
*(textP + selStart) = *insertPtr;
else
BlockMove(insertPtr, textP + selStart, insertLen);
itsGapPosition += insertLen;
itsGapLength -= insertLen;
}
else {
CloseGap();
#endif
SetHandleSize(itsTextHandle, itsTextLength + insertLen);
FailMemError();
textP = *itsTextHandle;
BlockMove(textP + selStart, textP + selStart + insertLen, itsTextLength - selStart);
BlockMove(insertPtr, textP + selStart, insertLen);
#if qPEUseInsertionGap
}
#endif
itsTextLength += insertLen;
// Adjust the line starts and redraw the new text if necessary
AdjustLineStarts(selStart, insertLen, numInsertCRs);
if (!redraw)
RefreshTextAfter(selStart, (numInsertCRs == 0));
// Set the selection to the end of the inserted text
SetSelection(selStart + insertLen, selEnd + insertLen, kNoRedraw);
// Redraw the entire pane if requested
if (redraw)
Refresh();
}
/******************************************************************************
DeleteText
Internal method to delete a range of characters. Called by
DeleteTextRange and ReplaceSelection.
******************************************************************************/
void CPEditText::DeleteText(
long start,
long end,
long numDeleteCRs,
Boolean redraw)
{
long length;
long selStart;
long selEnd;
length = end - start;
if (length > 0) {
#if qPEUseInsertionGap
// Check if we can delete the selected text by simply adding it to the insertion gap
if ((itsGapLength == 0) || (start == itsGapPosition) || (end == itsGapPosition)) {
itsGapPosition = start;
itsGapLength += length;
itsTextLength -= length;
}
else {
// Close up the insertion gap and remove the selected text from the text handle
CloseGap();
#endif
BlockMove(*itsTextHandle + end, *itsTextHandle + start, itsTextLength - end);
itsTextLength -= length;
SetHandleSize(itsTextHandle, itsTextLength);
#if qPEUseInsertionGap
}
#endif
// Adjust the line starts array
AdjustLineStarts(start, -length, -numDeleteCRs);
// Adjust the current selection
GetSelection(&selStart, &selEnd);
if (selStart >= start) {
if (selStart >= end)
selStart -= length;
else
selStart = start;
}
if (selEnd >= start) {
if (selEnd >= end)
selEnd -= length;
else
selEnd = selStart;
}
SetSelection(selStart, selEnd, kNoRedraw);
// Refresh the necessary text if requested
if (redraw)
RefreshTextAfter(start, (numDeleteCRs == 0));
}
}
/******************************************************************************
RefreshTextAfter
Internal method to redraw the text following the specified character.
******************************************************************************/
void CPEditText::RefreshTextAfter(
long afterPos,
Boolean refreshOnlyLine)
{
long startLine;
long endLine;
long vertInset = VertInset();
LongRect lr;
Rect r;
Prepare();
// Redraw the text after the given offset on the line
startLine = FindLine(afterPos);
if (refreshOnlyLine)
endLine = startLine;
else {
endLine = (frame.bottom - vertInset) / itsLineHeight;
endLine = Min(endLine, itsNumLines - 1);
}
DrawLineRange(startLine, endLine, afterPos - GetLineStart(startLine), kEraseText);
// Erase the area below the last text line, if necessary
if (!refreshOnlyLine) {
SetLongRect(&lr, frame.left, (endLine + 1) * itsLineHeight + vertInset, frame.right, frame.bottom);
if (lr.bottom > lr.top) {
FrameToQDR(&lr, &r);
EraseRect(&r);
}
}
}
/******************************************************************************
DrawLineRange
Internal method to draw the specified range of text lines. Assumes
the pane has been prepared.
******************************************************************************/
void CPEditText::DrawLineRange(
long startLine,
long endLine,
long startLineOffset,
Boolean erase)
{
Boolean showInvisibles = fShowInvisibles;
char textHState;
register char tab;
Handle invisH;
long line;
long lineStart;
long lineEnd;
long firstChar;
long boundsHExtent;
long horizInset = HorizInset();
#if qPEUseInsertionGap
long gapPosition = itsGapPosition;
long gapLength = itsGapLength;
#endif
LongPt textPt;
LongRect lr;
Point penPt;
Point startPt;
#if qPEUseInsertionGap
Ptr gapP;
#endif
Rect eraseRect;
RgnHandle clipRgn;
register Ptr textP;
register short numChars;
register short index;
register short tabWidth = itsTabWidth;
ShortHandle widthsH = NULL;
short width;
short *widthsP;
short fontAscent = itsFontAscent;
short lineHeight = itsLineHeight;
// Lock down the text handle
textHState = HGetState(itsTextHandle);
HLock(itsTextHandle);
// If we are erasing, save the current clipping region
if (erase) {
GetClip(cSaveClipRgn);
clipRgn = NewRgn();
}
// Compute the vertical coordinate for the first line of text
textPt.v = startLine * lineHeight + fontAscent + VertInset();
// Draw the specified lines of text
for (line = startLine; line <= endLine; ++line) {
// Compute the number of characters to draw
lineStart = GetLineStart(line);
lineEnd = GetLineEnd(line);
firstChar = lineStart + startLineOffset;
numChars = lineEnd - firstChar;
if ((line == startLine) && (startLineOffset > 0)) {
widthsH = MeasureLineWidths(line);
textPt.h = (*widthsH)[startLineOffset] + horizInset;
}
else {
widthsH = NULL;
textPt.h = horizInset;
}
FrameToQD(&textPt, &startPt);
// Erase the background behind the text, if desired
if (erase) {
SetLongRect(&lr, (line == startLine && startLineOffset > 0 ? textPt.h : frame.left),
textPt.v - fontAscent, frame.right, textPt.v - fontAscent + lineHeight);
FrameToQDR(&lr, &eraseRect);
// Adjust the clipping region and set things so that the entire line
// is redrawn -- this is so that italic characters don't get clipped
RectRgn(clipRgn, &eraseRect);
SectRgn(clipRgn, cSaveClipRgn, clipRgn);
SetClip(clipRgn);
firstChar = lineStart;
numChars = lineEnd - lineStart;
textPt.h = horizInset;
FrameToQD(&textPt, &startPt);
}
ForgetHandle(widthsH);
// If invisible characters are visible, make a copy of the text
// and convert all the invisible characters
if (showInvisibles) {
invisH = CopyTextRange(lineStart, lineEnd);
numChars = ConvertInvisibles(invisH, numChars);
HLock(invisH);
textP = *invisH;
#if qPEUseInsertionGap
gapP = NULL;
gapPosition = gapLength = 0;
#endif
}
else {
#if qPEUseInsertionGap
textP = *itsTextHandle;
gapP = textP + gapPosition;
textP += firstChar;
if (firstChar > gapPosition)
textP += gapLength;
#else
textP = *itsTextHandle + firstChar;
#endif
}
// Now draw the line of text
FrameToQD(&textPt, &penPt);
MoveTo(penPt.h, penPt.v);
if (erase)
EraseRect(&eraseRect);
#if THINK_C
asm {
clr.w index ; reset index
move.b #kTab,tab ; keep tab character in register for speed
@Loop:
cmp.w numChars,index ; have we drawn all the text?
bge.s @Done ; branch if we have
#if qPEUseInsertionGap
cmpa.l gapP,textP ; are we at the gap?
bne.s @NotAtGap ; branch if not
@AtGap:
bsr.s @DrawText ; else draw the text to the left of the gap
adda.l gapLength,textP ; and skip over the gap
@NotAtGap:
#endif
addq.w #1,index ; else bump the index
cmp.b (textP)+,tab ; is character from text a tab?
bne.s @Loop ; loop if not
@IsTab:
subq.w #1,textP ; decrement ptr to text
subq.w #1,index ; decrement # of characters to draw
bsr.s @DrawText ; draw the text to the left of the tab
movea.l thePort,a0 ; get current GrafPtr
move.l OFFSET(GrafPort,pnLoc)(a0),penPt ; get pen location
tst.b showInvisibles ; are we displaying invisible characters?
beq.s @NoInvis1 ; branch if not
move.w #kInvisTab,-(a7) ; else push invisible tab character
_DrawChar ; and draw it
@NoInvis1:
moveq #0,d0 ; clear out all of D0
move.w penPt.h,d0 ; get horiz pen location
sub.w startPt.h,d0 ; subtract starting location
divu.w tabWidth,d0 ; divide by tab width
addq.w #1,d0 ; add one to tab width
mulu.w tabWidth,d0 ; get pixel offset
add.w startPt.h,d0 ; add starting location
move.w d0,-(a7) ; push horiz coordinate
move.w penPt.v,-(a7) ; push vert coordinate
_MoveTo ; move pen location
addq.w #1,textP ; bump ptr to text
subq.w #1,numChars ; subtract 1 from length of text
bra.s @Loop ; and loop
@DrawText:
tst.w index ; any text to draw?
beq.s @NoText ; skip if not
movea.l textP,a0 ; get ptr to next character
suba.w index,a0 ; subtract index
move.l a0,-(a7) ; push ptr to text
clr.w -(a7) ; push offset into text
move.w index,-(a7) ; push length of text to draw
_DrawText ; call DrawText
sub.w index,numChars ; subtract from length of text
clr.w index ; reset index to 0
@NoText:
rts
@Done:
bsr.s @DrawText ; draw rest of text
}
#else
#if qPEUseInsertionGap
AsmDrawLineRange(textP, numChars, tabWidth, gapP, gapLength, startPt, showInvisibles);
#else
AsmDrawLineRange(textP, numChars, tabWidth, NULL, 0, startPt, showInvisibles);
#endif
#endif
// Dispose the temporary handle for invisible characters
if (showInvisibles)
DisposHandle(invisH);
// Increment the text vertical coordinate
textPt.v += lineHeight;
}
// Restore the previous clipping region and the previous state of the text handle
if (erase) {
SetClip(cSaveClipRgn);
DisposeRgn(clipRgn);
}
HSetState(itsTextHandle, textHState);
}
/******************************************************************************
HiliteTextRange
Internal method to highlight the given range of text. Assumes
the pane has been prepared.
******************************************************************************/
void CPEditText::HiliteTextRange(long start, long end)
{
Boolean isActive = fReallyActive;
long startLine;
long endLine;
LongPt startPt;
LongPt endPt;
LongRect hiliteRect;
PenState penState;
Rect qdRect;
RgnHandle hiliteRgn;
RgnHandle rectRgn;
short hSpan;
short vSpan;
short fontAscent = itsFontAscent;
short lineHeight = itsLineHeight;
if (isActive || fOutlineHilite) {
// Outline the selection range if the pane is not active
if (!isActive) {
hiliteRgn = NewRgn();
rectRgn = NewRgn();
}
// Get the starting and ending selection lines and the number of lines
// spanned by the frame
startLine = FindLine(start);
endLine = FindLine(end);
GetFrameSpan(&hSpan, &vSpan);
// Take a quick exit if the selection is not visible within the frame span
if ((startLine >= position.v + vSpan) || (endLine < position.v))
return;
// Adjust the starting and ending selection lines if they are outside the frame
if (startLine < position.v) {
startLine = position.v;
start = GetLineStart(startLine);
}
if (endLine > position.v + vSpan) {
endLine = position.v + vSpan;
end = GetLineEnd(endLine);
}
// Get the frame coordinates corresponding to the
// start and end of the selection range
GetCharPoint(start, &startPt);
GetCharPoint(end, &endPt);
// Adjust the horizontal coordinates if the either of the starting
// or ending characters lies at the beginning of a line
if (start == GetLineStart(startLine))
startPt.h = frame.left;
if (end == GetLineStart(endLine))
endPt.h = frame.left;
// Check for and handle a multiple-line selection range
if (startPt.v != endPt.v) {
// Highlight the first line of the selection range
SetLongRect(&hiliteRect, startPt.h, startPt.v - fontAscent, frame.right, startPt.v - fontAscent + lineHeight);
FrameToQDR(&hiliteRect, &qdRect);
if (isActive)
HiliteHook(&qdRect);
else {
qdRect.left -= 1;
RectRgn(rectRgn, &qdRect);
UnionRgn(rectRgn, hiliteRgn, hiliteRgn);
}
// Highlight the middle lines of the selection range
hiliteRect.left = frame.left;
if (isActive) {
FrameToQDR(&hiliteRect, &qdRect);
while (++startLine < endLine) {
qdRect.top += lineHeight;
qdRect.bottom += lineHeight;
HiliteHook(&qdRect);
}
}
else {
hiliteRect.top += lineHeight;
hiliteRect.left -= 1;
hiliteRect.bottom = endPt.v - fontAscent + 1;
FrameToQDR(&hiliteRect, &qdRect);
RectRgn(rectRgn, &qdRect);
UnionRgn(rectRgn, hiliteRgn, hiliteRgn);
}
SetLongPt(&startPt, hiliteRect.left, endPt.v);
}
// Hilite the last part of the selection range
SetLongRect(&hiliteRect, startPt.h, endPt.v - fontAscent, endPt.h, endPt.v - fontAscent + lineHeight);
FrameToQDR(&hiliteRect, &qdRect);
if (isActive)
HiliteHook(&qdRect);
else {
if (qdRect.right > qdRect.left + 1) {
qdRect.bottom += 1;
RectRgn(rectRgn, &qdRect);
UnionRgn(rectRgn, hiliteRgn, hiliteRgn);
}
// Intersect the highlight region with the pane frame
FrameToQDR(&frame, &qdRect);
RectRgn(rectRgn, &qdRect);
SectRgn(rectRgn, hiliteRgn, hiliteRgn);
DisposeRgn(rectRgn);
// Outline the highlight region
GetPenState(&penState);
PenNormal();
PenMode(patXor);
FrameRgn(hiliteRgn);
DisposeRgn(hiliteRgn);
SetPenState(&penState);
}
}
}
/******************************************************************************
GetTextWidth
Internal method to return the number of pixels between the
specified characters on a given line.
******************************************************************************/
short CPEditText::GetTextWidth(long line, short startPos, short endPos)
{
ShortHandle widthsH;
short width;
if (endPos > startPos) {
widthsH = MeasureLineWidths(line);
width = (*widthsH)[endPos] - (*widthsH)[startPos];
DisposHandle((Handle)widthsH);
}
else
width = 0;
return width;
}
/******************************************************************************
MeasureLineWidths
Internal method to compute and return an array of pixel distances
for the given line.
******************************************************************************/
ShortHandle CPEditText::MeasureLineWidths(long line)
{
return MeasureTextWidths(GetLineStart(line), GetLineEnd(line), MAXINT);
}
/******************************************************************************
MeasureTextWidths
Internal method to compute and return an array of pixel distances
for the text between the given offsets. The maxWidth parameter
specifies the maximum pixel width that the client is interested in.
******************************************************************************/
ShortHandle CPEditText::MeasureTextWidths(long startPos, long endPos, short maxWidth)
{
char textHState;
#if THINK_C
register char tab;
#endif
Handle invisH = NULL;
#if qPEUseInsertionGap
long gapPosition = itsGapPosition;
long gapLength = itsGapLength;
Ptr gapP;
#endif
register Ptr textP;
register short numChars;
#if THINK_C
register short index;
short lastWidth;
#endif
register short tabWidth = itsTabWidth;
register short *widthsP;
ShortHandle widthsH;
// Lock down the text handle and allocate space for the widths array
textHState = HGetState(itsTextHandle);
HLock(itsTextHandle);
numChars = (short)(endPos - startPos);
widthsH = (ShortHandle)NewHandle((numChars + 1) * sizeof(short));
HLock((Handle)widthsH);
widthsP = *widthsH;
// If "show invisible characters" is enabled, make a copy of the text
// and convert all the invisible characters
if (fShowInvisibles) {
invisH = CopyTextRange(startPos, endPos);
numChars = ConvertInvisibles(invisH, numChars);
HLock(invisH);
textP = *invisH;
#if qPEUseInsertionGap
gapP = NULL;
gapPosition = gapLength = 0;
#endif
}
else {
#if qPEUseInsertionGap
textP = *itsTextHandle;
gapP = textP + gapPosition;
textP += startPos;
if (startPos >= gapPosition)
textP += gapLength;
#else
textP = *itsTextHandle + startPos;
#endif
}
// Measure the text and save the pixel widths
Prepare();
#if THINK_C
asm {
clr.w index ; reset index
clr.w lastWidth ; set lastWidth to zero
move.b #kTab,tab ; keep tab character in register for speed
@Loop:
cmp.w numChars,index ; have we measured all the text?
bge.s @Done ; branch if we have
#if qPEUseInsertionGap
cmpa.l gapP,textP ; are we at the gap?
bne.s @NotAtGap ; branch if not
@AtGap:
bsr.s @MeasTxt ; else measure the text to the left of the gap
adda.l gapLength,textP ; and skip over the gap
@NotAtGap:
#endif
addq.w #1,index ; else bump the index
cmp.b (textP)+,tab ; is character from text a tab?
bne.s @Loop ; loop if not
@IsTab:
subq.w #1,textP ; decrement ptr to text
subq.w #1,index ; decrement # of characters to measure
bsr.s @MeasTxt ; measure the text to the left
moveq #0,d0 ; clear out all of d0
move.w lastWidth,d0 ; get total width of text so far
divu.w tabWidth,d0 ; divide by tab width
addq.w #1,d0 ; add one to tab width
mulu.w tabWidth,d0 ; compute width at next tab stop
addq.w #2,widthsP ; bump widthsP
move.w d0,(widthsP) ; save width of tab in widths array
move.w d0,lastWidth ; remember new total width
addq.w #1,textP ; bump ptr to text
subq.w #1,numChars ; decrement length of text
bra.s @Loop ; and loop
@MeasTxt:
move.w index,-(a7) ; push number of characters to measure
movea.l textP,a0 ; get ptr to next character in text
suba.w index,a0 ; subtract index
move.l a0,-(a7) ; push ptr to text to measure
move.l widthsP,-(a7) ; push ptr to current position in widths array
_MeasureText ; measure the text
move.w lastWidth,d0 ; keep lastWidth in d0
move.w index,d1 ; loop index in d1
@WidthLoop:
add.w d0,(widthsP)+ ; add lastWidth to element of widths array
dbra d1,@WidthLoop ; and loop
move.w -(widthsP),lastWidth ; save width of last character
sub.w index,numChars ; decrement length of text
clr.w index ; reset character count
rts ; and return to caller
@Done:
bsr.s @MeasTxt ; measure the rest of the text
}
#else
#if qPEUseInsertionGap
AsmMeasureTextWidths(textP, numChars, tabWidth, gapP, gapLength, widthsP, maxWidth);
#else
AsmMeasureTextWidths(textP, numChars, tabWidth, NULL, 0, widthsP, maxWidth);
#endif
#endif
// Clean up
HUnlock((Handle)widthsH);
HSetState(itsTextHandle, textHState);
ForgetHandle(invisH);
return widthsH;
}
/******************************************************************************
ConvertInvisibles
Internal method to convert any invisible characters in the given
text to their corresponding visible characters. The handle may
be resized if necessary, so the method returns the number of
characters in the resized handle.
******************************************************************************/
short CPEditText::ConvertInvisibles(Handle invisH, short numChars)
{
register Ptr invisP = *invisH;
register short count = numChars;
while (--count >= 0) {
if (*invisP <= kSpace) {
switch (*invisP) {
case kSpace:
*invisP = kInvisSpace;
break;
case kReturn:
*invisP = kInvisReturn;
break;
case kLineFeed:
*invisP = kInvisLineFeed;
break;
case kFormFeed:
*invisP = kInvisFormFeed;
break;
case kTab:
// Don't convert tabs here
break;
default:
*invisP = kInvisOther;
break;
}
}
++invisP;
}
return numChars; // since we don't yet resize the handle
}
/******************************************************************************
AdjustBounds
Internal method to adjust the bounds of a PEditText panorama.
Performed whenever something happens which can affect the number of
lines of text or the line width.
******************************************************************************/
void CPEditText::AdjustBounds()
{
LongRect newBounds;
newBounds.top = 0;
newBounds.left = 0;
newBounds.bottom = itsNumLines;
newBounds.right = (kDefaultBoundsWidth - 1) / hScale + 1;
SetBounds(&newBounds);
}
/******************************************************************************
CalcLineHeight
Internal method to calculate the line height and font ascent.
Called by SetFontNumber, SetFontSize, SetFontStyle and SetSpacingCmd.
******************************************************************************/
void CPEditText::CalcLineHeight()
{
FontInfo macFontInfo;
// Update itsLineHeight, itsFontAscent and itsMaxCharWidth
GetMacFontInfo(&macFontInfo);
itsLineHeight = macFontInfo.ascent + macFontInfo.descent + macFontInfo.leading;
itsFontAscent = macFontInfo.ascent;
itsMaxCharWidth = macFontInfo.widMax;
if (itsSpacingCmd == cmd1HalfSpace) {
itsLineHeight *= 3;
itsLineHeight /= 2;
}
else if (itsSpacingCmd == cmdDoubleSpace)
itsLineHeight *= 2;
CalcTabWidth();
// Refresh the contents of the pane
Refresh();
SetScales(itsMaxCharWidth, itsLineHeight);
AdjustBounds();
SetWholeLines(wholeLines);
Refresh();
}
/******************************************************************************
CalcTabWidth
Internal method to calculate the width of a tab. Called by
SetTabSpaces and CalcLineHeight.
******************************************************************************/
void CPEditText::CalcTabWidth()
{
Prepare();
itsTabWidth = itsTabSpaces * CharWidth(kSpace);
}
/******************************************************************************
CalcLineStarts
Internal method to calculate the line starts array.
******************************************************************************/
void CPEditText::CalcLineStarts()
{
Boolean origAlloc;
register Byte cr = kReturn;
char textHState;
register long charPos;
register long textLength = itsTextLength;
register long *lineStarts;
register long numLines;
#if qPEUseInsertionGap
long gapLength = itsGapLength;
long gapPosition = itsGapPosition;
register Ptr gapP;
#endif
register Ptr textP;
unsigned long startTicks = TickCount();
// Initialize our pointer to the text
textHState = HGetState(itsTextHandle);
HLock(itsTextHandle);
textP = *itsTextHandle;
#if qPEUseInsertionGap
gapP = textP + gapPosition;
#endif
// Count the number of lines in the text
charPos = itsTextLength;
numLines = 1;
while (--charPos >= 0) {
#if qPEUseInsertionGap
if (textP == gapP)
textP += gapLength;
#endif
if (*textP++ == cr)
++numLines;
}
// Reallocate the line starts array
// Here we intentionally make resizing the line starts handle
// a critical operation so that it is less likely to fail.
// The rationale is that resizing the text handle is far more
// likely to exceed available memory than resizing the line
// starts handle (since the line starts handle is normally much
// smaller), and falling back at this point is somewhat tricky
// (although by no means impossible.)
origAlloc = SetAllocation(kAllocCantFail);
SetHandleSize((Handle)itsLineStarts, numLines * sizeof(long));
(void)SetAllocation(origAlloc);
FailMemError();
// Update the number of lines
itsNumLines = numLines;
// Build the line starts array
lineStarts = *itsLineStarts + 1;
textP = *itsTextHandle;
charPos = 0;
while (++charPos <= textLength) {
#if qPEUseInsertionGap
if (textP == gapP)
textP += gapLength;
#endif
if (*textP++ == cr)
*lineStarts++ = charPos;
}
HSetState(itsTextHandle, textHState);
// Adjust the panorama bounds to match the number of lines in the text
AdjustBounds();
}
/******************************************************************************
AdjustLineStarts
Internal method to increment the line starts by the given amount,
starting at the given line.
******************************************************************************/
void CPEditText::AdjustLineStarts(long startChar, register long numCharsDelta, long numLinesDelta)
{
register Byte cr = kReturn;
register long *lineStarts;
register long count;
long startLine;
register Ptr textP;
#if qPEUseInsertionGap
register Ptr gapP;
#endif
// Find the line corresponding to the starting character offset
startLine = FindLine(startChar) + 1;
// Adjust the line starts array depending on the change in the number of lines
if (numLinesDelta == 0) {
// Bump the values in the line starts array, starting at startLine
lineStarts = *itsLineStarts + startLine;
count = itsNumLines - startLine + 1;
while (--count > 0)
*lineStarts++ += numCharsDelta;
}
else if (numLinesDelta > 0) {
Boolean origAlloc;
#if qPEUseInsertionGap
long gapPosition = itsGapPosition;
long gapLength = itsGapLength;
#endif
// Insert new entries in the line starts array starting at startLine
// (See comment in CalcLineStarts() method above for rationale here)
origAlloc = SetAllocation(kAllocCantFail);
SetHandleSize((Handle)itsLineStarts, (itsNumLines + numLinesDelta) * sizeof(long));
(void)SetAllocation(origAlloc);
FailMemError();
if (startLine < itsNumLines)
BlockMove(*itsLineStarts + startLine, *itsLineStarts + startLine + numLinesDelta, (itsNumLines - startLine) * sizeof(long));
itsNumLines += numLinesDelta;
// Calculate values for the new entries
lineStarts = *itsLineStarts + startLine;
textP = *itsTextHandle;
#if qPEUseInsertionGap
gapP = textP + gapPosition;
#endif
textP += startChar;
#if qPEUseInsertionGap
if (startChar >= gapPosition)
textP += gapLength;
#endif
count = numCharsDelta;
while (--count >= 0) {
#if qPEUseInsertionGap
if (textP == gapP)
textP += gapLength;
#endif
if (*textP++ == cr)
*lineStarts++ = startChar + (numCharsDelta - count);
}
// Adjust the values of the remaining entries
count = itsNumLines - (startLine + numLinesDelta);
while (--count >= 0)
*lineStarts++ += numCharsDelta;
}
else { // numLinesDelta < 0
// Delete entries from the line starts array starting at startLine
itsNumLines += numLinesDelta;
if (startLine < itsNumLines)
BlockMove(*itsLineStarts + startLine + (-numLinesDelta), *itsLineStarts + startLine, (itsNumLines - startLine) * sizeof(long));
SetHandleSize((Handle)itsLineStarts, itsNumLines * sizeof(long));
// Bump the values in the remaining entries
lineStarts = *itsLineStarts + startLine;
count = itsNumLines - startLine + 1;
while (--count > 0)
*lineStarts++ += numCharsDelta;
}
// Adjust the bounds rectangle if the number of lines changed
if (numLinesDelta != 0)
AdjustBounds();
}
/******************************************************************************
DrawCaret
Internal method to draw the text insertion caret.
******************************************************************************/
void CPEditText::DrawCaret()
{
LongPt caretPt;
LongRect caretRect;
Rect qdRect;
if (itsSelStart == itsSelEnd) {
GetCharPoint(itsSelStart, &caretPt);
SetLongRect(&caretRect, caretPt.h - 1, caretPt.v - itsFontAscent, caretPt.h, caretPt.v - itsFontAscent + itsLineHeight);
FrameToQDR(&caretRect, &qdRect);
CaretHook(&qdRect);
}
}
/******************************************************************************
ShowCaret
Internal method to show the text insertion caret if it is not visible.
******************************************************************************/
void CPEditText::ShowCaret()
{
if (!fCaretVisible) {
DrawCaret();
fCaretVisible = TRUE;
itsCaretTime = TickCount() + GetCaretTime();
}
}
/******************************************************************************
HideCaret
Internal method to hide the text insertion caret if it is visible.
******************************************************************************/
void CPEditText::HideCaret()
{
if (fCaretVisible) {
DrawCaret();
fCaretVisible = FALSE;
itsCaretTime = TickCount() + GetCaretTime();
}
}
/******************************************************************************
GetMacFontInfo
Return a QuickDraw FontInfo record for the font used by a PEditText
pane. This method changes the font family, style, and size of the
pane's port so the Toolbox trap GetFontInfo can be used.
******************************************************************************/
void CPEditText::GetMacFontInfo(FontInfo *macFontInfo)
{
ForceNextPrepare();
SetPort(macPort);
TextFont(itsTextFont);
TextFace(itsTextFace);
TextSize(itsTextSize);
GetFontInfo(macFontInfo);
}
/**** U T I L I T Y F U N C T I O N S ****/
/*============================================================================
CountCRs
Return the number of carriage return characters in the given text.
=============================================================================*/
static long CountCRs(register Ptr textP, register long numChars)
{
register Byte cr = kReturn;
register long numCRs = 0;
while (numChars--) {
if (*textP++ == cr)
++numCRs;
}
return numCRs;
}
/*============================================================================
GetGrayRGBColor
Get the grayishTextOr gray RGB color. Return TRUE if the color
was retrieved, FALSE otherwise.
=============================================================================*/
static Boolean GetGrayRGBColor(const Rect *localRect, RGBColor *grayColor, RGBColor *prevForeColor)
{
GDHandle targetDevice;
Rect globalRect;
RGBColor backColor;
if (gSystem.hasColorQD && IsColorPort(thePort) && GestaltHasAttr(gestaltQuickdrawFeatures, gestaltHasGrayishTextOr)) {
GetForeColor(prevForeColor);
GetBackColor(&backColor);
globalRect = *localRect;
LocalToGlobal(&topLeft(globalRect));
LocalToGlobal(&botRight(globalRect));
targetDevice = GetMaxDevice(&globalRect);
*grayColor = *prevForeColor;
return GetGray(targetDevice, &backColor, grayColor);
}
else
return FALSE;
}
/*============================================================================
GestaltHasAttr
Call Gestalt with the given selector and test the given bit in
the response.
=============================================================================*/
static Boolean GestaltHasAttr(OSType selector, short responseBit)
{
long response;
return (Gestalt(selector, &response) == noErr && (response & (1L << responseBit)) != 0);
}